#ifndef __AABBox_hpp__
#define __AABBox_hpp__

#include "Matrix4.hpp"
#include "Ray.hpp"

namespace ge {

//----------------------------------------------------------------------------//
// AABBox
//----------------------------------------------------------------------------//

class AABBox
{
 public:

     AABBox( void ) : min_bound( Math::MaxFloat ), max_bound( Math::MinFloat )
     {
     }

     AABBox( const AABBox& _aabb ) : min_bound( _aabb.min_bound ), max_bound( _aabb.max_bound )
     {
     }

     AABBox( const Vector3& _min, const Vector3& _max )
     {
          min_bound.SetMin( _min, _max );
          max_bound.SetMax( _min, _max );
     }

     AABBox operator * ( const Matrix4& _m ) const
     {
          const Vector3& _min = min_bound;
          const Vector3& _max = max_bound;
          AABBox _b;
          Vector3 _corner;
          _corner   = _min;   _b.AddPoint( _m * _corner ); // min, min, min
          _corner.z = _max.z; _b.AddPoint( _m * _corner ); // min, min, max 
          _corner.y = _max.y; _b.AddPoint( _m * _corner ); // min, max, max 
          _corner.z = _min.z; _b.AddPoint( _m * _corner ); // min, max, min 
          _corner.x = _max.x; _b.AddPoint( _m * _corner ); // max, max, min 
          _corner.z = _max.z; _b.AddPoint( _m * _corner ); // max, max, max 
          _corner.y = _min.y; _b.AddPoint( _m * _corner ); // max, min, max
          _corner.z = _min.z; _b.AddPoint( _m * _corner ); // max, min, min
          return _b;
     }

     friend AABBox operator * ( const Matrix4& _m, const AABBox& _aabb )
     {
          return _aabb * _m;
     }

     AABBox& Set( const AABBox& _aabb )
     {
          return (*this = _aabb);
     }

     AABBox& SetMinMax( const Vector3& _a, const Vector3& _b )
     {
          min_bound.SetMin( _a, _b );
          max_bound.SetMax( _a, _b );
          return *this;
     }

     AABBox& Set( const Vector3& _min, const Vector3& _max )
     {
          assert( _min < _max );
          min_bound.Set( _min );
          max_bound.Set( _max );
          return *this;
     }

     AABBox& SetCenterExtends( const Vector3& _center, const Vector3& _size )
     {
          return Set( _center - _size, _center + _size );
     }

     AABBox& FromMatrix( const Matrix4& _m )
     {
          Reset(    Vector3( -1, +1, +1 ) * _m );
          AddPoint( Vector3( +1, +1, +1 ) * _m );
          AddPoint( Vector3( -1, -1, +1 ) * _m );
          AddPoint( Vector3( +1, -1, +1 ) * _m );
          AddPoint( Vector3( -1, +1, -1 ) * _m );
          AddPoint( Vector3( +1, +1, -1 ) * _m );
          AddPoint( Vector3( -1, -1, -1 ) * _m );
          AddPoint( Vector3( +1, -1, -1 ) * _m );

          return *this;
     }

     AABBox& Reset( const Vector3& _pt )
     {
          return Set( _pt, _pt );
     }

     AABBox& AddPoint( const Vector3& _pt )
     {
          min_bound.SetMin( _pt );
          max_bound.SetMax( _pt );
          return *this;
     }

     AABBox& AddVertices( const void* _data, uint _count_vertices, uint _stride = 0, uint _first_offset = 0 )
     {
          if( _data && _count_vertices > 0 )
          {
               union { const uint8* p; const Vector3* v; } _vertices = { ((const uint8*)_data) + _first_offset };

               for( uint i = 0, _step = sizeof( Vector3 ) + _stride; i < _count_vertices; ++ i )
               {
                    min_bound.SetMin( *_vertices.v );
                    max_bound.SetMax( *_vertices.v );
                    _vertices.p += _step;
               }
          }

          return *this;
     }

     AABBox& operator = ( const AABBox& _other )
     {
          min_bound = _other.min_bound;
          max_bound = _other.max_bound;
          return *this;
     }

     AABBox& operator += ( const Vector3& _point )
     {
          return AddPoint( _point );
     }

     AABBox operator + ( const Vector3& _point ) const
     {
          return AABBox( *this ).AddPoint( _point );
     }

     friend AABBox operator + ( const Vector3& _point, const AABBox& _aabb )
     {
          return _aabb + _point;
     }

     AABBox& operator += ( const AABBox& _aabb )
     {
          return AddPoint( _aabb.min_bound ).AddPoint( _aabb.max_bound );
     }

     AABBox operator + ( const AABBox& _aabb ) const
     {
          return AABBox( *this ).AddPoint( _aabb.min_bound ).AddPoint( _aabb.max_bound );
     }

     const Vector3& MinBound( void ) const
     {
          return min_bound;
     }

     const Vector3& MaxBound( void ) const
     {
          return max_bound;
     }

     Vector3 Size( void ) const
     {
          return max_bound - min_bound;
     }

     Vector3 Extents( void ) const
     {
          return max_bound - min_bound;
     }

     Vector3 HalfSize( void ) const
     {
          return ( max_bound - min_bound ) * 0.5f;
     }

     Vector3 Center( void ) const 
     {
          return ( max_bound + min_bound ) * 0.5f;
     }

     float Diagonal( void ) const
     {
          return ( max_bound - min_bound ).Length();
     }

     float Radius( void ) const
     {
          return Diagonal() * 0.5f;
     }

     float Width( void ) const
     {
          return max_bound.x - min_bound.x;
     }

     float Height( void ) const
     {
          return max_bound.y - min_bound.y;
     }

     float Depth( void ) const
     {
          return max_bound.z - min_bound.z;
     }

     float Volume( void ) const
     {
          return ( max_bound - min_bound ).LengthSq();
     }

     AABBox& SetNull( void )
     {
          min_bound = Math::MaxFloat;
          max_bound = Math::MinFloat;
          return *this;
     }

     AABBox& SetInfinite( void )
     {
          min_bound = Math::MaxFloat;
          max_bound = Math::MinFloat;
          return *this;
     }

     bool IsNull( void ) const
     {
          return min_bound > max_bound;
     }

     bool IsFinite( void ) const
     {
          return min_bound <= max_bound;
     }

     bool Contains( const Vector3& _point ) const
     {
          if( IsNull()  ) return false;

          if( _point.x < min_bound.x || _point.x > max_bound.x ||
              _point.y < min_bound.y || _point.y > max_bound.y ||
              _point.z < min_bound.z || _point.z > max_bound.z )
          {
               return false;
          }

          return true;
     }

     bool Contains( const AABBox& _aabb ) const
     {
          if( IsNull() || _aabb.IsNull() ) return false;

          if( max_bound.x >= _aabb.max_bound.x && max_bound.y >= _aabb.max_bound.y && max_bound.z >= _aabb.max_bound.z &&
              min_bound.x <= _aabb.min_bound.x && min_bound.y <= _aabb.min_bound.y && min_bound.z <= _aabb.min_bound.z )
          {
               return true;
          }

          return false;
     }

     bool Intersects( const AABBox& _aabb ) const
     {
          if( IsNull() || _aabb.IsNull() ) return false;

          if( max_bound.x < _aabb.min_bound.x || max_bound.y < _aabb.min_bound.y || max_bound.z < _aabb.min_bound.z ||
              min_bound.x > _aabb.max_bound.x || min_bound.y > _aabb.max_bound.y || min_bound.z > _aabb.max_bound.z )
          {
               return false;
          }

          return true;
     }

     bool Intersects( const Ray& _ray, float* _distance = 0, Vector3* _point = 0 ) const
     {
          float _d = 0.0f;
          bool _hit = false;

          if( _ray.origin > min_bound && _ray.origin < max_bound && !_distance && !_point ) 
          {
               _hit = true;
          }
          else if( IsFinite() )
          {
               float _t, _low_t = 0.0f;
               Vector3 _hit_point;
               const Vector3& _min = min_bound;
               const Vector3& _max = max_bound;
               const Vector3& _origin = _ray.origin;
               const Vector3& _dir = _ray.dir;

#define _TEST( x, y, z ) \
               if( ( _hit == false || _distance || _point ) && ( _origin.x <= _min.x && _dir.x > 0.0f ) ) \
               { \
	                   _t = (_min.x - _origin.x) / _dir.x; \
	                   if( _t >= 0.0f ) \
	                   { \
		                       _hit_point = _origin + _dir * _t; \
		                       if( _hit_point.y >= _min.y && _hit_point.y <= _max.y && \
			                          _hit_point.z >= _min.z && _hit_point.z <= _max.z && ( !_hit || _t < _low_t ) ) \
		                       { \
			                           _hit = true; \
			                           _low_t = _t; \
		                       } \
	                   } \
               } \
               if( ( _hit == false || _distance || _point ) && ( _origin.x >= _max.x && _dir.x < 0.0f ) ) \
               { \
	                   _t = (_max.x - _origin.x) / _dir.x; \
	                   if( _t >= 0.0f ) \
	                   { \
		                       _hit_point = _origin + _dir * _t; \
		                       if( _hit_point.y >= _min.y && _hit_point.y <= _max.y && \
			                          _hit_point.z >= _min.z && _hit_point.z <= _max.z && ( !_hit || _t < _low_t ) ) \
		                       { \
			                           _hit = true; \
			                           _low_t = _t; \
		                       } \
	                   } \
               }

               _TEST( x, y, z )
               _TEST( y, x, z )
               _TEST( z, x, y )
#undef _TEST
          }

          if( _distance ) *_distance = _d;
          if( _point ) *_point = _ray.Point( _d );

          return _hit;
     }
     
     /* 
     
          6 ----------- 7
         /|            /|
        / |max_bound  / |
       /  |        \ /  |         y+
      5 ----------- 4   |         |
      |   |         |   |         |
      |   0 --------|-- 1         | 
      |  / \        |  /          o-------x+
      | /  min_bound| /          /
      |/            |/          /
      3 ----------- 2          z+

     */
     enum Corner
     {
          LeftBottomFar = 0,// min_bound
          RightBottomFar,
          RightBottomNear,
          LeftBottomNear,
          RightTopNear, // max_bound
          LeftTopNear,
          LeftTopFar,
          RightTopFar,
     }; 


     void GetAllCorners( const void* _data, uint _stride = 0, uint _first_offset = 0 ) const
     {
          if( _data )
          {
               union { uint8* p; Vector3* v; } _vertices = { ((uint8*)_data) + _first_offset };
               uint _step = sizeof( Vector3 ) + _stride; 
               *_vertices.v = min_bound; _vertices.p += _step; // LeftBottomFar
               *_vertices.v = Vector3( max_bound.x, min_bound.y, min_bound.z ); _vertices.p += _step; // RightBottomFar
               *_vertices.v = Vector3( max_bound.x, min_bound.y, max_bound.z ); _vertices.p += _step; // RightBottomNear
               *_vertices.v = Vector3( min_bound.x, min_bound.y, max_bound.z ); _vertices.p += _step; // LeftBottomNear
               *_vertices.v = max_bound; _vertices.p += _step; // RightTopNear
               *_vertices.v = Vector3( min_bound.x, max_bound.y, max_bound.z ); _vertices.p += _step; // LeftTopNear
               *_vertices.v = Vector3( min_bound.x, max_bound.y, min_bound.z ); _vertices.p += _step; // LeftTopFar
               *_vertices.v = Vector3( max_bound.x, max_bound.y, min_bound.z );; // RightTopFar
          }
     }

 public:

     static const uint16 Lines[24];
     static const uint16 Quads[24]; // ccw

 public:

     Vector3 min_bound;
     Vector3 max_bound;
};

} // namespace ge

#endif//__AABBox_hpp__
